package com.ease.util {

   /**
    * Class that represents a time interval (duration of time or elapsed time).
    *
    * <p>The largest unit of time supported is a day. Both positive and negative time
    * intervals are supported.
    *
    * <p>A <code>TimeSpan</code> object can be created from given start and end dates, or
    * given number of days, hours, minutes, seconds, milliseconds.
    *
    * <listing version="3.0">
    *    var startTime:Date = new Date();
    *    // perform an operation
    *    var endTime:Date = new Date();
    *
    *    _logger.debug("Elapsed time: " + TimeSpan.fromDates(startTime, endTime));
    * </listing>
    */
   public class TimeSpan {

      //---------------------------------------------------------------------------
      // Constants

      public static const MILLISECONDS_IN_SECOND:Number = 1000;
      public static const MILLISECONDS_IN_MINUTE:Number = 60 * MILLISECONDS_IN_SECOND;
      public static const MILLISECONDS_IN_HOUR:Number = 60 * MILLISECONDS_IN_MINUTE;
      public static const MILLISECONDS_IN_DAY:Number = 24 * MILLISECONDS_IN_HOUR;

      //---------------------------------------------------------------------------
      // Private Fields

      private var _totalMilliseconds:Number;

      //---------------------------------------------------------------------------
      // Constructor

      public function TimeSpan(milliseconds:Number) {
         _totalMilliseconds = Math.floor(milliseconds);
      }

      //---------------------------------------------------------------------------
      // Factory Methods

      /**
       * Returns a <code>TimeSpan</code> object that represents the difference between
       * two given dates.
       *
       * <p><code>end<code> date is excluded when calculating the time span.
       *
       * @param start
       *    The start date of the time span, inclusive.
       * @param end
       *    The end date of the time span, exclusive.
       * @return
       *    The created <code>TimeSpan</code> object representing the difference between
       *    the dates.
       */
      public static function fromDates(start:Date, end:Date):TimeSpan {
         return new TimeSpan(end.time - start.time);
      }

      /**
       * Returns a <code>TimeSpan</code> object that represents a specified number of
       * milliseconds.
       *
       * @param milliseconds
       *    The number of milliseconds in the time span.
       * @return
       *    The created <code>TimeSpan</code> object.
       */
      public static function fromMilliseconds(milliseconds:Number):TimeSpan {
         return new TimeSpan(milliseconds);
      }

      /**
       * Returns a <code>TimeSpan</code> object that represents a specified number of
       * seconds.
       *
       * @param seconds
       *    The number of seconds in the time span.
       * @return
       *    The created <code>TimeSpan</code> object.
       */
      public static function fromSeconds(seconds:Number):TimeSpan {
         return new TimeSpan(seconds * MILLISECONDS_IN_SECOND);
      }

      /**
       * Returns a <code>TimeSpan</code> object that represents a specified number of
       * minutes.
       *
       * @param minutes
       *    The number of minutes in the time span.
       * @return
       *    The created <code>TimeSpan</code> object.
       */
      public static function fromMinutes(minutes:Number):TimeSpan {
         return new TimeSpan(minutes * MILLISECONDS_IN_MINUTE);
      }

      /**
       * Returns a <code>TimeSpan</code> object that represents a specified number of
       * hours.
       *
       * @param hours
       *    The number of hours in the time span.
       * @return
       *    The created <code>TimeSpan</code> object.
       */
      public static function fromHours(hours:Number):TimeSpan {
         return new TimeSpan(hours * MILLISECONDS_IN_HOUR);
      }

      /**
       * Returns a <code>TimeSpan</code> object that represents a specified number of
       * days.
       *
       * @param days
       *    The number of days in the time span.
       * @return
       *    The created <code>TimeSpan</code> object.
       */
      public static function fromDays(days:Number):TimeSpan {
         return new TimeSpan(days * MILLISECONDS_IN_DAY);
      }

      //---------------------------------------------------------------------------
      // Public Properties

      /**
       * Gets the days component of the time span.
       */
      public function get days():int {
         return int(_totalMilliseconds / MILLISECONDS_IN_DAY);
      }

      /**
       * Gets the hours component of the time span.
       */
      public function get hours():int {
         return int(_totalMilliseconds / MILLISECONDS_IN_HOUR) % 24;
      }

      /**
       * Gets the minutes component of the time span.
       */
      public function get minutes():int {
         return int(_totalMilliseconds / MILLISECONDS_IN_MINUTE) % 60;
      }

      /**
       * Gets the seconds component of the time span.
       */
      public function get seconds():int {
         return int(_totalMilliseconds / MILLISECONDS_IN_SECOND) % 60;
      }

      /**
       * Gets the milliseconds component of the time span.
       */
      public function get milliseconds():int {
         return int(_totalMilliseconds) % 1000;
      }

      /**
       * Gets the value of the current <code>TimeSpan</code> object expressed in
       * whole and fractional days.
       */
      public function get totalDays():Number {
         return _totalMilliseconds / MILLISECONDS_IN_DAY;
      }

      /**
       * Gets the value of the current <code>TimeSpan</code> object expressed in
       * whole and fractional hours.
       */
      public function get totalHours():Number {
         return _totalMilliseconds / MILLISECONDS_IN_HOUR;
      }

      /**
       * Gets the value of the current <code>TimeSpan</code> object expressed in
       * whole and fractional minutes.
       */
      public function get totalMinutes():Number {
         return _totalMilliseconds / MILLISECONDS_IN_MINUTE;
      }

      /**
       * Gets the value of the current <code>TimeSpan</code> object expressed in
       * whole and fractional seconds.
       */
      public function get totalSeconds():Number {
         return _totalMilliseconds / MILLISECONDS_IN_SECOND;
      }

      /**
       * Gets the value of the current <code>TimeSpan</code> object expressed in
       * milliseconds.
       */
      public function get totalMilliseconds():Number {
         return _totalMilliseconds;
      }

      //---------------------------------------------------------------------------
      // Public Methods

      /**
       * Returns a string representation of the current <code>TimeSpan</code> object.
       *
       * <p>The returned string has the following format:
       * [-][d days, ]hh:mm:ss.ms,
       * where the minus sign and number of days are omitted, if not needed.
       */
      public function toString():String {
         var positive:Boolean = this.totalMilliseconds >= 0;

         var days:int = Math.abs(this.days);
         var hours:int = Math.abs(this.hours);
         var minutes:int = Math.abs(this.minutes);
         var seconds:int = Math.abs(this.seconds);
         var milliseconds:int = Math.abs(this.milliseconds);

         // speev: We generate the string directly. This is 2.5 times faster
         // (with debug version of the Flash Player 10.0.32.18) than using a
         // format string like "{0}:{1}:{2}.{3}" and calling StringUtil.substitute
         var retVal:String = "";
         if (!positive) {
            retVal += "-";
         }
         if (days != 0) {
            retVal += days.toString() + " days, ";
         }
         retVal += (hours < 10 ? "0" : "") + hours.toString() + ":";
         retVal += (minutes < 10 ? "0" : "") + minutes.toString() + ":";
         retVal += (seconds < 10 ? "0" : "") + seconds.toString() + ".";
         retVal += (milliseconds < 10 ? "00" : (milliseconds < 100 ? "0": "")) +
            milliseconds.toString();

         return retVal;
      }
   }
}